﻿using System;
using System.IO;
using OpenSSL;
using OpenSSL.Core;
using OpenSSL.Crypto;
using OpenSSL.X509;

namespace xxpl1.Cert
{
    public class CertUtils
    {
        private static string certFolder;

        public static void Init()
        {
            certFolder = AppDomain.CurrentDomain.BaseDirectory;
            certFolder = Path.Combine(certFolder, "cert/");

            if (!Directory.Exists(certFolder))
            {
                Directory.CreateDirectory(certFolder);
            }

            if (!File.Exists(Path.Combine(certFolder, "_ca.crt"))
                || !File.Exists(Path.Combine(certFolder, "_ca.key")))
            {
                MakeCA();
            }
        }

        public static SslStream WrapStream(string host, Stream stream)
        {
            if (host.Contains(":"))
                host = host.Substring(0, host.IndexOf(":"));
            X509Certificate certificate = GetCert(host);
            var sslStream = new SslStream(stream, false);
            sslStream.AuthenticateAsServer(certificate);
            return sslStream;
        }

        private static X509Certificate GetCert(string host)
        {
            string certName = Path.Combine(certFolder, host + ".crt");

            if (!File.Exists(certName))
            {
                X509Certificate certificate = GetCertFromFile("_ca");

                MakeCert(host, certificate.PrivateKey, certificate);
            }
            return GetCertFromFile(host);
        }

        private static X509Certificate GetCertFromFile(string host)
        {
            BIO certBio = BIO.File(Path.Combine(certFolder, host + ".crt"), "r");
            var certificate = new X509Certificate(certBio);

            BIO keyBio = BIO.File(Path.Combine(certFolder, host + ".key"), "r");
            RSA rsa = RSA.FromPrivateKey(keyBio);

            certificate.PrivateKey = new CryptoKey(rsa);

            return certificate;
        }

        private static void MakeCA()
        {
            CryptoKey pkey = CreateKey(2048);
            ;
            X509Request req = CreateCertRequest(pkey, "TTProxy CA", MessageDigest.SHA1);
            X509Certificate cert = CreateCert(req, pkey, req.Subject, 0, DateTime.Now, DateTime.Now.AddYears(20), MessageDigest.SHA1);

            File.WriteAllText(Path.Combine(certFolder, "_ca.key"), pkey.GetRSA().PrivateKeyAsPEM);
            File.WriteAllText(Path.Combine(certFolder, "_ca.crt"), cert.PEM);
        }

        private static void MakeCert(string host, CryptoKey cakey, X509Certificate cacrt)
        {
            var fileSerialNumber = new FileSerialNumber("_serial");
            CryptoKey pkey = CreateKey(1024);
            X509Request req = CreateCertRequest(pkey, host, MessageDigest.SHA1);
            X509Certificate cert = CreateCert(req, cakey, cacrt.Subject, fileSerialNumber.Next(), DateTime.Now, DateTime.Now.AddYears(20), MessageDigest.SHA1);

            File.WriteAllText(Path.Combine(certFolder, host + ".key"), pkey.GetRSA().PrivateKeyAsPEM);
            File.WriteAllText(Path.Combine(certFolder, host + ".crt"), cert.PEM);
        }

        private static X509Certificate CreateCert(X509Request req, CryptoKey pKey, X509Name issuer, int serial, DateTime notBefore, DateTime notAfter, MessageDigest digest)
        {
            var cert = new X509Certificate();
            cert.SerialNumber = serial;
            cert.NotBefore = notBefore;
            cert.NotAfter = notAfter;
            cert.Issuer = issuer;
            cert.Subject = req.Subject;
            cert.PublicKey = req.PublicKey;
            cert.Sign(pKey, digest);
            return cert;
        }

        private static X509Request CreateCertRequest(CryptoKey pKey, string host, MessageDigest messageDigest)
        {
            var subject = new X509Name();
            subject.Country = "CN";
            subject.OrganizationUnit = "TTProxy Root";
            subject.StateOrProvince = "Internet";
            subject.Locality = "Cernet";
            subject.Organization = "TTProxy";
            subject.Common = host;

            var x509Request = new X509Request(1, subject, pKey);

            x509Request.PublicKey = pKey;

            x509Request.Sign(pKey, messageDigest);

            return x509Request;
        }

        private static CryptoKey CreateKey(int bits)
        {
            var rsa = new RSA();
            var bigNumber = new BigNumber(65537u);
            rsa.GenerateKeys(bits, bigNumber, null, null);
            return new CryptoKey(rsa);
        }
    }
}